package com.sskj.spk.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * @author zhang.xiaoming
 * @ClassName RsaUtil
 * @Description Rsa分段加密实现
 * @date 2018年3月13日 下午6:56:30
 */
public class RsaUtil {
    private static final String CHARSET = "UTF-8";
    private static final String RSA_ALGORITHM = "RSA";
    /**
     * 针对 keysize = 1024 RSA最大加密明文大小 117
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    /**
     * 针对 keysize = 1024  RSA最大解密密文大小  128
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * keysize 1024
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static KeyPair createKey() throws NoSuchAlgorithmException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        generator.initialize(1024);
        KeyPair keyPair = generator.generateKeyPair();
        return keyPair;
    }

    /**
     * 得到公钥
     *
     * @param publicKey 密钥字符串（经过base64编码）
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String publicKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(org.google.binary.Base64.decodeBase64(publicKey));
        RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
        return key;
    }

    /**
     * 得到私钥
     *
     * @param privateKey 密钥字符串（经过base64编码）
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String privateKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(org.google.binary.Base64.decodeBase64(privateKey));
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
        return key;
    }

    /**
     * 公钥加密
     *
     * @param data      数据
     * @param publicKey 公钥
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     * @throws InvalidKeySpecException
     */
    public static String publicEncrypt(String data, String publicKey) throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeySpecException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        PublicKey pk = getPublicKey(publicKey);
        cipher.init(Cipher.ENCRYPT_MODE, pk);
        byte[] dataByte = data.getBytes(CHARSET), cache;
        int dataLength = dataByte.length, offSet = 0, i = 0;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            // 数据分段加密
            while (dataLength - offSet > 0) {
                if (dataLength - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(dataByte, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(dataByte, offSet, dataLength - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptData = out.toByteArray();
            return org.google.binary.Base64.encodeBase64String(encryptData);
        }
    }

    /**
     * 私钥解密
     * <p>
     * 解码关键(因为原始数据先geyBytes后再做Base64编码，此处getBytes后再还原回方可解密)
     *
     * @param s       字符串
     * @param privateKey 私钥
     * @return
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public static String privateDecrypt(String s, String privateKey)
            throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
            IllegalBlockSizeException, BadPaddingException, IOException {
        PrivateKey pbk = getPrivateKey(privateKey);
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, pbk);
        String[] split = s.split("=");
        StringBuilder stringBuilder = new StringBuilder();
        for (String data : split) {

            //Base64做对称解码(一定要先把数据还原，否则解密失败---因为原始数据先geyBytes后再做Base64编码，此处getBytes后再还原回去)
            byte[] dataByte = org.google.binary.Base64.decodeBase64(data.getBytes()), cache;
            int dataLength = dataByte.length, offSet = 0, i = 0;
            try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
                // 数据分段解密
                while (dataLength - offSet > 0) {
                    if (dataLength - offSet > MAX_DECRYPT_BLOCK) {
                        cache = cipher.doFinal(dataByte, offSet, MAX_DECRYPT_BLOCK);
                    } else {
                        cache = cipher.doFinal(dataByte, offSet, dataLength - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    i++;
                    offSet = i * MAX_DECRYPT_BLOCK;
                }
                byte[] decryptedData = out.toByteArray();
                stringBuilder.append(new String(decryptedData, CHARSET));
            }
        }
        return stringBuilder.toString();

    }

    /**
     * 私钥加密
     *
     * @param data       数据
     * @param privateKey 私钥
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeySpecException
     * @throws IOException
     */
    public static String privateEncrypt(String data, String privateKey)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException, InvalidKeySpecException, IOException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        PrivateKey pk = getPrivateKey(privateKey);
        cipher.init(Cipher.ENCRYPT_MODE, pk);
        byte[] dataByte = data.getBytes(CHARSET), cache;
        int dataLength = dataByte.length, offSet = 0, i = 0;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            // 数据分段加密
            while (dataLength - offSet > 0) {
                if (dataLength - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(dataByte, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(dataByte, offSet, dataLength - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();

            return org.google.binary.Base64.encodeBase64String(encryptedData);
        }
    }

    /**
     * 公钥解密
     * <p>
     * 解码关键(因为原始数据先geyBytes后再做Base64编码，此处getBytes后再还原回方可解密)
     *
     * @param data      数据
     * @param publicKey 公钥
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeySpecException
     * @throws IOException
     */
    public static String publicDecrypt(String data, String publicKey)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException, InvalidKeySpecException, IOException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
        cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
        //Base64做对称解码
        byte[] dataByte = org.google.binary.Base64.decodeBase64(data.getBytes()), cache;
        int dataLength = dataByte.length, offSet = 0, i = 0;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
            while (dataLength - offSet > 0) {
                if (dataLength - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(dataByte, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(dataByte, offSet, dataLength - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            return new String(decryptedData, CHARSET);
        }
    }
}